/*
 * Copyright (C) 2000-2008 Jens Nixdorf and various contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.mopore.jat.core.externalplugins;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.swing.JOptionPane;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mopore.jat.Main;
import org.mopore.jat.gui.ProgressDialog;
import org.mopore.jat.plugin.IHook;
import org.mopore.jat.plugin.IJatPluginInternal;
import org.mopore.jat.plugin.PluginException;
import org.mopore.jat.util.Common;

public class ExternalPluginZipInstaller {
	
	private static final int MININUM_WAIT_TIME = 1500;
	private final Log log = LogFactory.getLog(getClass().getName());
	
	public ExternalPluginZipInstaller( 
			File pluginZipFile, 
			ProgressDialog dialog, 
			ExternalPluginManager manager ) throws PluginException {
		
		File tmpDir = prepareTemporaryDir();
		
		try {
			long startTime = System.currentTimeMillis();
			
			dialog.setProgress(5);
			tmpDir.mkdir();
			dialog.setProgress(10);
			copyAndUnzipToDir( pluginZipFile, tmpDir );
			dialog.setProgress(35);
			String pluginName = getPluginName( tmpDir );
			log.info( "plugin name: " + pluginName );
			
			boolean alreadyInstalled = 
				manager.checkAlreadyInstalled( pluginName );
			
			if ( alreadyInstalled ) {
				dialog.setVisible( false );
				String message = "A plugin with the name '" + pluginName + 
				"' is already installed. Please remove the installed plugin " +
				"first.";
				log.info( message );
				JOptionPane.showMessageDialog( dialog, message, 
						"error adding plugin", JOptionPane.WARNING_MESSAGE );
			}
			else {
				File targetDir = createTargetDirectoryFile( pluginName );
				log.info( "plugin directory name: " + targetDir );
				dialog.setProgress(40);
				copyAndUnzipToDir(pluginZipFile, targetDir);
				dialog.setProgress(65);
				Common.deleteDir( tmpDir );
				dialog.setProgress(70);
	
				manager.postLoadPlugin( targetDir );
				
				long endTime = System.currentTimeMillis();
				long duration = endTime - startTime;
				if ( duration < MININUM_WAIT_TIME ) {
					dialog.setProgress(100);
					long waitTime = ( MININUM_WAIT_TIME - duration );
					Thread.sleep( waitTime );
				}
				manager.addEntryToPluginList( pluginName );
			}
			dialog.dispose();
		} catch (Exception e) {
			String message = "Can not install " + pluginZipFile.getName();
			log.error( message, e );
			JOptionPane.showMessageDialog( dialog, message, 
					"error adding external plugin", JOptionPane.ERROR_MESSAGE);
			dialog.dispose();
			throw new PluginException( message, e );
		}
		finally {
			removeTemporaryDir(tmpDir);
		}
	}


	private void removeTemporaryDir(File tmpDir) {
		if ( tmpDir != null ) {
			if ( tmpDir.exists() ) {
				try {
					Common.deleteDir( tmpDir );
				}
				catch (Exception e) {
					String errorMessage = "Final clean up does not worked";
					log.error( errorMessage, e );
				}
			}
		}
	}


	private File prepareTemporaryDir() {
		String randomName = String.valueOf( System.currentTimeMillis() );
		if ( ! Main.TMP_DIR.exists()) {
			Main.TMP_DIR.mkdir();
		}
		File tmpDir = new File( Main.TMP_DIR, randomName);
		return tmpDir;
	}


	private File createTargetDirectoryFile(String name) {
		long time = ( System.currentTimeMillis() / 1000 );
		String fileName = name + "_" + String.valueOf( time ); 
		return new File( Main.PLUGINS_DIR, fileName );
	}
	
	
	
	

	private String getPluginName(File tmpDir) throws PluginException {
		
		List<URL> urlList = new ArrayList<URL>();

		File pluginFile = new File( tmpDir, "plugin.jar" );
		
		// get plugin.jar
		try {
			URL pluginUrl = pluginFile.toURI().toURL();
			urlList.add( pluginUrl );
		} catch (MalformedURLException e) {
			String errorNoUrl = "could not get url to plugin.jar for: " + 
			tmpDir.getName();
			log.error( errorNoUrl );
			throw new PluginException( errorNoUrl, e );
		}
		
		// get supporting jars of plugin
		File[] pluginLibs = collectExternalPluginLibs( tmpDir );
		for (File pluginLib : pluginLibs) {
			try {
				URL url = pluginLib.toURI().toURL();
				urlList.add( url );
			} catch (MalformedURLException e) {
				String errorNoUrl = "could not get url for: " + pluginLib.getName();
				log.error( errorNoUrl );
				throw new PluginException( errorNoUrl, e );
			}
		}
		
		int urlArrayLength = urlList.size();
		URL[] urls = urlList.toArray( new URL[ urlArrayLength ] );
		
		ClassLoader classLoader = new URLClassLoader( urls );
		Class<?> c;
		try {
			c = classLoader.loadClass( IHook.hookClassName );
			IHook hook = (IHook) c.newInstance();
			IJatPluginInternal plugin = hook.getPlugin();
			String pluginName = plugin.getPluginName();
        
	        classLoader = null;
	        c = null;
	        hook = null;
	        plugin = null;
	        System.gc();
	        Thread.sleep( 1000 );
        
	        if ( tmpDir.exists() ) {
		        boolean result = Common.deleteDir( tmpDir );
		        if ( result == false ) {
		        	throw new PluginException( "could not delete: " + tmpDir );
		        }
	        }
       		return pluginName;
		} catch (Exception e) {
			String noHookMessage = "Can not load plugin for: " + tmpDir.getName();
			log.error( noHookMessage, e );
			throw new PluginException( noHookMessage, e );
		}
	}





	private void copyAndUnzipToDir( File zipFile, File targetDir ) throws Exception {

		targetDir.mkdir();
		
		int filesToCopy = 0;
		
		File targetLibsDir = new File(targetDir, "libs");
		if (!targetLibsDir.exists()) {
			targetLibsDir.mkdir();
		}

		ZipFile zip = new ZipFile(zipFile);
		
		Enumeration<? extends ZipEntry> entries = zip.entries();
		while (entries.hasMoreElements()) {
			
			File outputFile = null;
			
			ZipEntry entry = entries.nextElement();
			boolean isPLugin = entry.getName().equals("plugin.jar");

			if (isPLugin) {
				outputFile = new File( targetDir, "plugin.jar" );
			} else {
				boolean isInLibs = entry.getName().startsWith("libs/");
				boolean isJar = entry.getName().endsWith( ".jar" );
				
				if ( isInLibs && isJar ) {
					String fileName = entry.getName().substring( 5 );
					outputFile = new File( targetLibsDir, fileName );
				}
			}

			if ( outputFile != null ){
				filesToCopy++;
				InputStream in = zip.getInputStream(entry);
				System.out.println( outputFile );
				copy(in, outputFile);
			}
		}
		zip.close();
	}

	private static void copy(InputStream in, File outFile)
			throws FileNotFoundException, IOException {

		OutputStream out = new FileOutputStream(outFile);

		byte[] buffer = new byte[1024];
		while (true) {
			int bytesRead = in.read(buffer);
			if (bytesRead == -1)
				break;
			out.write(buffer, 0, bytesRead);
		}

		in.close();
		out.close();
	}



	private File[] collectExternalPluginLibs(File pluginFolder) {
		
		File pluginLibsFolder = new File( pluginFolder, "libs" );
		File[] supportLibs = pluginLibsFolder.listFiles(new FilenameFilter(){
			public boolean accept(File jarFile, String name) {
				return name.endsWith(".jar");
			}}
		);
		return supportLibs;
	}
}
